package com.thp.singleton;
/**
 * 懒汉式 (静态内部类)
 * 静态内部类的优点是:外部类加载的时候并不需要去加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存
 * 		即当Singleton4第一次被加载时,并不需要去加载LazyHolder,只有当getInstance()方法第一次被调用时,
 * 		才会去初始哈INSTANCE
 * 		第一次调用getInstance()方法会导致虚拟机加载LazyHolder类,这种方法不仅能够确保线程安全,
 * 		也能够保证单例的唯一性,同样也延迟了单例的实例化.
 * @author Tommy
 *
 */
public class Singleton4 {
	
	// 1. 先声明一个静态内部类
	// private 私有保证别人不能修改
	// static 保证全局唯一
	private static class LazyHolder {
		/**
		 * 加上final主要是为了防止内部误操作  比如我们如果使用了CGLIB的代理模式(子类继承的实现方式)
		 * 子类是可以拿到LazyHolder,那么就有可能会操作这个内部类
		 */
		private static final Singleton4 INSTANCE = new Singleton4();
	}
	
	private Singleton4() {}
	
	
	// 3.提供静态方法获取实例
	// final 确保别人不能覆盖
	public static final Singleton4 getInstance() {
		return LazyHolder.INSTANCE;
	}
	
	
	/**
	 * 静态内部类又是如何实现线程安全的呢？首先，我们先了解下类的加载时机。
	 * 
	 * 	类加载时机：JAVA虚拟机在[有且仅有]的5种场景下会对类进行初始:
	 * 		1. 遇到new、getstatic、setstatic或者invikestatic这4个字节码指令时，
	 * 		对应的java代码场景为：
	 * 			new一个关键字或者一个实例化对象时、
	 * 			读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、
	 * 			调用一个类的静态方法时
	 * 		2.	使用java.lang.reflect包的方法对类进行反射调用的时候，
	 * 			如果类没进行初始化，需要先调用其初始化方法进行初始化。
	 * 
	 * 		3. 当初始化一个类时，如果其父类还未进行初始化，会先触发其父类的初始化。
	 * 
	 * 		4. 当虚拟机启动时，用户需要指定一个要执行的主类(包含main()方法的类)，虚拟机会先初始化这个类。
	 * 
	 * 		5 .当使用JDK 1.7等动态语言支持时，
	 * 				如果一个java.lang.invoke.MethodHandle实例最后的解析结果
	 * 				REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄，
	 * 				并且这个方法句柄所对应的类没有进行过初始化，则需要先触发其初始化。
	 * 
	 * 这5种情况被称为是类的主动引用，注意，
	 * 这里《虚拟机规范》中使用的限定词是"有且仅有"，
	 * 那么，除此之外的所有引用类都不会对类进行初始化，称为被动引用。
	 * 静态内部类就属于被动引用的行列。
	 */
	
	
	/**
	 * 我们再回头来看一下getInsatnce()方法,调用的LazyHolder.INSTANCE,获取的是LazyHolder里的INSATNCE对象,
	 * 跟DCL方法不同的是,getInstance()方法并没有去多次去new对象,故不管多少个线程去调用getInstance()方法,取的都是同一个INSTANCE对象
	 * 而不用去重新创建.
	 * 当getInstance()方法被调用时,LazyHolder才在Singleton4的运行常量池里,把符号引用替换为直接引用,这是静态对象INSTANCE才真正被创建.
	 * 然后再被getInstance()方法返回回去.
	 * 
	 * 那么INSTANCE在创建过程中又是如何保证线程安全的呢？在《深入理解JAVA虚拟机》中，有这么一句话:
	 * 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步，如果多个线程同时去初始化一个类，
	 * 那么只会有一个线程去执行这个类的<clinit>()方法，其他线程都需要阻塞等待，直到活动线程执行<clinit>()方法完毕。
	 * 如果在一个类的<clinit>()方法中有耗时很长的操作，就可能造成多个进程阻塞(需要注意的是，其他线程虽然会被阻塞，但如果执行<clinit>()方法后，
	 * 其他线程唤醒之后不会再次进入<clinit>()方法。同一个加载器下，一个类型只会初始化一次。)，在实际应用中，这种阻塞往往是很隐蔽的。
	 * 
	 * 那么，是不是可以说静态内部类单例就是最完美的单例模式了呢？
	 * 
	 * 实不然，静态内部类也有着一个致命的缺点，就是传参的问题，由于是静态内部类的形式去创建单例的，故外部无法传递参数进去，
	 * 例如Context这种参数，所以，我们创建单例时，可以在静态内部类与DCL模式里自己斟酌。
	 * 
	 */
	
	
	
}
